/*
 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.imageio.plugins.gif;

import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import org.w3c.dom.Node;

/**
 * Class which adds utility DOM element attribute access methods to
 * <code>IIOMetadata</code> for subclass use.
 */
abstract class GIFMetadata extends IIOMetadata {

  /**
   * Represents an undefined value of integer attributes.
   */
  static final int UNDEFINED_INTEGER_VALUE = -1;

  //
  // Note: These attribute methods were shamelessly lifted from
  // com.sun.imageio.plugins.png.PNGMetadata and modified.
  //

  // Shorthand for throwing an IIOInvalidTreeException
  protected static void fatal(Node node, String reason)
      throws IIOInvalidTreeException {
    throw new IIOInvalidTreeException(reason, node);
  }

  // Get an integer-valued attribute
  protected static String getStringAttribute(Node node, String name,
      String defaultValue,
      boolean required,
      String[] range)
      throws IIOInvalidTreeException {
    Node attr = node.getAttributes().getNamedItem(name);
    if (attr == null) {
      if (!required) {
        return defaultValue;
      } else {
        fatal(node, "Required attribute " + name + " not present!");
      }
    }
    String value = attr.getNodeValue();

    if (range != null) {
      if (value == null) {
        fatal(node,
            "Null value for " + node.getNodeName() +
                " attribute " + name + "!");
      }
      boolean validValue = false;
      int len = range.length;
      for (int i = 0; i < len; i++) {
        if (value.equals(range[i])) {
          validValue = true;
          break;
        }
      }
      if (!validValue) {
        fatal(node,
            "Bad value for " + node.getNodeName() +
                " attribute " + name + "!");
      }
    }

    return value;
  }


  // Get an integer-valued attribute
  protected static int getIntAttribute(Node node, String name,
      int defaultValue, boolean required,
      boolean bounded, int min, int max)
      throws IIOInvalidTreeException {
    String value = getStringAttribute(node, name, null, required, null);
    if (value == null || "".equals(value)) {
      return defaultValue;
    }

    int intValue = defaultValue;
    try {
      intValue = Integer.parseInt(value);
    } catch (NumberFormatException e) {
      fatal(node,
          "Bad value for " + node.getNodeName() +
              " attribute " + name + "!");
    }
    if (bounded && (intValue < min || intValue > max)) {
      fatal(node,
          "Bad value for " + node.getNodeName() +
              " attribute " + name + "!");
    }
    return intValue;
  }

  // Get a float-valued attribute
  protected static float getFloatAttribute(Node node, String name,
      float defaultValue,
      boolean required)
      throws IIOInvalidTreeException {
    String value = getStringAttribute(node, name, null, required, null);
    if (value == null) {
      return defaultValue;
    }
    return Float.parseFloat(value);
  }

  // Get a required integer-valued attribute
  protected static int getIntAttribute(Node node, String name,
      boolean bounded, int min, int max)
      throws IIOInvalidTreeException {
    return getIntAttribute(node, name, -1, true, bounded, min, max);
  }

  // Get a required float-valued attribute
  protected static float getFloatAttribute(Node node, String name)
      throws IIOInvalidTreeException {
    return getFloatAttribute(node, name, -1.0F, true);
  }

  // Get a boolean-valued attribute
  protected static boolean getBooleanAttribute(Node node, String name,
      boolean defaultValue,
      boolean required)
      throws IIOInvalidTreeException {
    Node attr = node.getAttributes().getNamedItem(name);
    if (attr == null) {
      if (!required) {
        return defaultValue;
      } else {
        fatal(node, "Required attribute " + name + " not present!");
      }
    }
    String value = attr.getNodeValue();
    // Allow lower case booleans for backward compatibility, #5082756
    if (value.equals("TRUE") || value.equals("true")) {
      return true;
    } else if (value.equals("FALSE") || value.equals("false")) {
      return false;
    } else {
      fatal(node, "Attribute " + name + " must be 'TRUE' or 'FALSE'!");
      return false;
    }
  }

  // Get a required boolean-valued attribute
  protected static boolean getBooleanAttribute(Node node, String name)
      throws IIOInvalidTreeException {
    return getBooleanAttribute(node, name, false, true);
  }

  // Get an enumerated attribute as an index into a String array
  protected static int getEnumeratedAttribute(Node node,
      String name,
      String[] legalNames,
      int defaultValue,
      boolean required)
      throws IIOInvalidTreeException {
    Node attr = node.getAttributes().getNamedItem(name);
    if (attr == null) {
      if (!required) {
        return defaultValue;
      } else {
        fatal(node, "Required attribute " + name + " not present!");
      }
    }
    String value = attr.getNodeValue();
    for (int i = 0; i < legalNames.length; i++) {
      if (value.equals(legalNames[i])) {
        return i;
      }
    }

    fatal(node, "Illegal value for attribute " + name + "!");
    return -1;
  }

  // Get a required enumerated attribute as an index into a String array
  protected static int getEnumeratedAttribute(Node node,
      String name,
      String[] legalNames)
      throws IIOInvalidTreeException {
    return getEnumeratedAttribute(node, name, legalNames, -1, true);
  }

  // Get a String-valued attribute
  protected static String getAttribute(Node node, String name,
      String defaultValue, boolean required)
      throws IIOInvalidTreeException {
    Node attr = node.getAttributes().getNamedItem(name);
    if (attr == null) {
      if (!required) {
        return defaultValue;
      } else {
        fatal(node, "Required attribute " + name + " not present!");
      }
    }
    return attr.getNodeValue();
  }

  // Get a required String-valued attribute
  protected static String getAttribute(Node node, String name)
      throws IIOInvalidTreeException {
    return getAttribute(node, name, null, true);
  }

  protected GIFMetadata(boolean standardMetadataFormatSupported,
      String nativeMetadataFormatName,
      String nativeMetadataFormatClassName,
      String[] extraMetadataFormatNames,
      String[] extraMetadataFormatClassNames) {
    super(standardMetadataFormatSupported,
        nativeMetadataFormatName,
        nativeMetadataFormatClassName,
        extraMetadataFormatNames,
        extraMetadataFormatClassNames);
  }

  public void mergeTree(String formatName, Node root)
      throws IIOInvalidTreeException {
    if (formatName.equals(nativeMetadataFormatName)) {
      if (root == null) {
        throw new IllegalArgumentException("root == null!");
      }
      mergeNativeTree(root);
    } else if (formatName.equals
        (IIOMetadataFormatImpl.standardMetadataFormatName)) {
      if (root == null) {
        throw new IllegalArgumentException("root == null!");
      }
      mergeStandardTree(root);
    } else {
      throw new IllegalArgumentException("Not a recognized format!");
    }
  }

  protected byte[] getColorTable(Node colorTableNode,
      String entryNodeName,
      boolean lengthExpected,
      int expectedLength)
      throws IIOInvalidTreeException {
    byte[] red = new byte[256];
    byte[] green = new byte[256];
    byte[] blue = new byte[256];
    int maxIndex = -1;

    Node entry = colorTableNode.getFirstChild();
    if (entry == null) {
      fatal(colorTableNode, "Palette has no entries!");
    }

    while (entry != null) {
      if (!entry.getNodeName().equals(entryNodeName)) {
        fatal(colorTableNode,
            "Only a " + entryNodeName + " may be a child of a " +
                entry.getNodeName() + "!");
      }

      int index = getIntAttribute(entry, "index", true, 0, 255);
      if (index > maxIndex) {
        maxIndex = index;
      }
      red[index] = (byte) getIntAttribute(entry, "red", true, 0, 255);
      green[index] = (byte) getIntAttribute(entry, "green", true, 0, 255);
      blue[index] = (byte) getIntAttribute(entry, "blue", true, 0, 255);

      entry = entry.getNextSibling();
    }

    int numEntries = maxIndex + 1;

    if (lengthExpected && numEntries != expectedLength) {
      fatal(colorTableNode, "Unexpected length for palette!");
    }

    byte[] colorTable = new byte[3 * numEntries];
    for (int i = 0, j = 0; i < numEntries; i++) {
      colorTable[j++] = red[i];
      colorTable[j++] = green[i];
      colorTable[j++] = blue[i];
    }

    return colorTable;
  }

  protected abstract void mergeNativeTree(Node root)
      throws IIOInvalidTreeException;

  protected abstract void mergeStandardTree(Node root)
      throws IIOInvalidTreeException;
}
